﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using VmcRemote.Common.Interfaces;
using VmcRemote.Common;
using System.Threading;
using System.Net.Sockets;

namespace Tcp
{
    public class TcpConnection : IConnection
    {
        public const int NoInactivityTimeout = 0;

        private Timer inactivityTimer;
        private bool activity;
        private object activityLock = new object();

        TcpClient tcpServer;

        public TcpConnection(string host, int port) : this(host, port, NoInactivityTimeout) { }

        public TcpConnection(string host, int port, int inactivityTimeout)
        {
            Host = host;
            Port = port;
            InactivityTimeout = inactivityTimeout;

            this.Status = ConnectionStatus.Closed;
            InitialiseConnection();
        }

        #region IConnection Members

        public string Host { get; private set; }

        public int Port { get; private set; }

        public int InactivityTimeout { get; private set; }

        public ConnectionStatus Status { get; private set; }

        public event EventHandler<ConnectionStatusChangedEventArgs> ConnectionStatusChanged;

        public void SendCommand(string commandText)
        {
            SendCommandInternal(commandText, true);
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                lock (activityLock)
                {
                    if (InactivityTimeout != null)
                        inactivityTimer.Dispose();
                    CloseConnection();
                }
            }

        }

        ~TcpConnection()
        {
            Dispose(false);
        }

        #endregion

        #region Inactivitiy Timer
        void timer_Tick(object state)
        {
            if (!activity)
            {
                lock (activityLock)
                {
                    if (!activity)
                    {
                        CloseConnection();
                    }
                }
            }
            activity = false;
        }
        #endregion

        #region Connection
        private void OnConnectionStatusChanged(ConnectionStatus status)
        {
            Status = status;
            if (ConnectionStatusChanged != null)
                ConnectionStatusChanged(this, new ConnectionStatusChangedEventArgs { Status = status });
        }

        private void InitialiseConnection()
        {
            lock (activityLock)
            {
                CloseConnection();

                OnConnectionStatusChanged(ConnectionStatus.Connecting);
                try
                {
                    tcpServer = new TcpClient(Host, Port);
                }
                catch { }

                if (tcpServer != null)
                    OnConnectionStatusChanged(ConnectionStatus.Open);
                else
                    OnConnectionStatusChanged(ConnectionStatus.Closed);

                if (Status == ConnectionStatus.Open && inactivityTimer == null && InactivityTimeout > NoInactivityTimeout)
                    inactivityTimer = new Timer(timer_Tick, null, InactivityTimeout, InactivityTimeout);
            }
        }

        private void CloseConnection()
        {
            if (Status != ConnectionStatus.Open) return;
            lock (activityLock)
            {
                if (tcpServer != null)
                {
                    tcpServer.Close();
                    tcpServer = null;
                    OnConnectionStatusChanged(ConnectionStatus.Closed);
                }
            }
        }
        #endregion

        #region Commands
        private void SendCommandInternal(string command, bool retryOnFailure)
        {
            lock (activityLock)
            {
                activity = true;
                if (Status == ConnectionStatus.Closed) InitialiseConnection();
                try
                {
                    if (!string.IsNullOrEmpty(command))
                    {
                        ASCIIEncoding encoding = new ASCIIEncoding();
                        byte[] bytes = encoding.GetBytes(command + (command.EndsWith("\r") ? string.Empty : "\r"));
                        tcpServer.GetStream().Write(bytes, 0, bytes.Length);
                    }
                }
                catch
                {
                    if (retryOnFailure)
                        SendCommandInternal(command, false);
                    else
                        throw;
                }
            }
        }
        #endregion
    }
}
